POLY_BASIC

Overview

The POLY_BASIC function fits standard polynomial models to data using non-linear least squares regression. It provides eight commonly used polynomial models ranging from a simple horizontal baseline to a ninth-order polynomial, making it suitable for a wide variety of curve fitting applications.

This function leverages scipy.optimize.curve_fit from the SciPy library, which uses the Levenberg-Marquardt algorithm to minimize the sum of squared residuals between the model and observed data. The underlying implementation is provided through the MINPACK Fortran library, a well-established numerical optimization package.

The available polynomial models include:

  • Horizontal Baseline: A constant value y = y_0
  • Linear Slope Intercept: y = A + Bx
  • Quadratic Parabola: y = A + Bx + Cx^2
  • Cubic Polynomial: y = A + Bx + Cx^2 + Dx^3
  • Fourth/Fifth/Ninth Order Polynomials: Higher-degree polynomials for complex curves
  • Linear Through X Intercept: y = a(x - b), where b is the x-intercept

For a polynomial of degree n, the general form is:

y = \sum_{i=0}^{n} a_i x^i = a_0 + a_1 x + a_2 x^2 + \cdots + a_n x^n

The function returns fitted parameter values along with their standard errors, which are computed from the covariance matrix of the parameter estimates. The standard error for each parameter is derived as \sigma_i = \sqrt{\text{diag}(\mathbf{C})_i}, where \mathbf{C} is the covariance matrix. Note that these error estimates assume the model is approximately linear near the optimal solution; for highly nonlinear models, the uncertainty estimates may be less accurate.

When selecting a polynomial order, consider that higher-degree polynomials can capture more complex patterns but may lead to overfitting on noisy data. A good practice is to start with lower-order polynomials and increase complexity only if the fit quality is insufficient. For more information on polynomial curve fitting and least squares methods, see the NumPy polyfit documentation and the SciPy least_squares documentation.

This example function is provided as-is without any representation of accuracy.

Excel Usage

=POLY_BASIC(xdata, ydata, poly_basic_model)
  • xdata (list[list], required): The xdata value
  • ydata (list[list], required): The ydata value
  • poly_basic_model (str, required): The poly_basic_model value

Returns (list[list]): 2D list [param_names, fitted_values, std_errors], or error string.

Examples

Example 1: Horizontal baseline constant fitting

Inputs:

poly_basic_model xdata ydata
horizontal_baseline 0.1 0
1.325 0
2.55 0
3.775 0
5 0

Excel formula:

=POLY_BASIC("horizontal_baseline", {0.1;1.325;2.55;3.775;5}, {0;0;0;0;0})

Expected output:

y0
0
0

Example 2: Linear slope-intercept fitting

Inputs:

poly_basic_model xdata ydata
linear_slope_intercept 0.1 2.9001769517921048
1.325 4.128674639240271
2.55 5.486408315144006
3.775 6.852272017107344
5 7.978703373645173

Excel formula:

=POLY_BASIC("linear_slope_intercept", {0.1;1.325;2.55;3.775;5}, {2.9001769517921048;4.128674639240271;5.486408315144006;6.852272017107344;7.978703373645173})

Expected output:

A B
2.788 1.051
0.05913 0.01918

Example 3: Quadratic parabola fitting

Inputs:

poly_basic_model xdata ydata
quadratic_parabola 0.1 3.393939102193672
1.325 8.311251676273942
2.55 17.539630409846307
3.775 30.939801209148083
5 52.11736115916023

Excel formula:

=POLY_BASIC("quadratic_parabola", {0.1;1.325;2.55;3.775;5}, {3.393939102193672;8.311251676273942;17.539630409846307;30.939801209148083;52.11736115916023})

Expected output:

A B C
3.58 0.8948 1.747
0.8464 0.7906 0.1491

Example 4: Cubic polynomial fitting

Inputs:

poly_basic_model xdata ydata
cubic_polynomial 0.1 10.311749963977787
1.325 34.61031843830554
2.55 110.9897726101281
3.775 293.1639187614614
5 688.2374981176258

Excel formula:

=POLY_BASIC("cubic_polynomial", {0.1;1.325;2.55;3.775;5}, {10.311749963977787;34.61031843830554;110.9897726101281;293.1639187614614;688.2374981176258})

Expected output:

A B C D
6.585 31.11 -15.44 7.29
7.232 14.49 7.09 0.9148

Example 5: Fourth order polynomial fitting

Inputs:

poly_basic_model xdata ydata
fourth_order_polynomial 0.1 23.145744352965757
1.325 20.50260900857478
2.55 215.71661385437147
3.775 820.3314595740266
5 2138.279376640648

Excel formula:

=POLY_BASIC("fourth_order_polynomial", {0.1;1.325;2.55;3.775;5}, {23.145744352965757;20.50260900857478;215.71661385437147;820.3314595740266;2138.279376640648})

Expected output:

A0 A1 A2 A3 A4
27.8 -48.75 21.78 5.931 1.709

Example 6: Fifth order polynomial fitting

Inputs:

poly_basic_model xdata ydata
fifth_order_polynomial -2 -7.7
-1.5 -0.56875
-1 1.4
-0.5 1.1875
0 0.5
0.5 0.14375
1 0.4
1.5 1.4
2 3.5

Excel formula:

=POLY_BASIC("fifth_order_polynomial", {-2;-1.5;-1;-0.5;0;0.5;1;1.5;2}, {-7.7;-0.56875;1.4;1.1875;0.5;0.14375;0.4;1.4;3.5})

Expected output:

A0 A1 A2 A3 A4 A5
0.5 -1.2 0.75 0.6 -0.35 0.1
7.281e-16 1.459e-15 9.993e-16 1.307e-15 2.351e-16 2.501e-16

Example 7: Ninth order polynomial fitting

Inputs:

poly_basic_model xdata ydata
ninth_order_polynomial -1.5 6.74
-1.2 2.987673
-0.9 1.654173
-0.6 1.145836
-0.3 0.919425
0 0.8
0.3 0.728117
0.6 0.680883
0.9 0.648897
1.2 0.617295
1.5 0.493789
1.8 -0.150114

Excel formula:

=POLY_BASIC("ninth_order_polynomial", {-1.5;-1.2;-0.9;-0.6;-0.3;0;0.3;0.6;0.9;1.2;1.5;1.8}, {6.74;2.987673;1.654173;1.145836;0.919425;0.8;0.728117;0.680883;0.648897;0.617295;0.493789;-0.150114})

Expected output:

a0 a1 a2 a3 a4 a5 a6 a7 a8 a9
0.8 -0.3 0.25 -0.2 0.15 -0.1 0.075 -0.05 0.025 -0.01
2.981e-7 0.00000107 0.000002894 0.000004644 0.000006643 0.000006711 0.000005025 0.000003922 0.000001167 7.908e-7

Example 8: Linear through x-intercept fitting

Inputs:

poly_basic_model xdata ydata
linear_through_x_intercept 0.1 -2.4941794119730596
1.325 0.7233145313435664
2.55 4.279283682520016
3.775 7.856545759090661
5 10.806723121451643

Excel formula:

=POLY_BASIC("linear_through_x_intercept", {0.1;1.325;2.55;3.775;5}, {-2.4941794119730596;0.7233145313435664;4.279283682520016;7.856545759090661;10.806723121451643})

Expected output:

a b
2.754 1.012
0.05024 0.04226

Python Code

import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math

def poly_basic(xdata, ydata, poly_basic_model):
    """
    Fits poly_basic models to data using scipy.optimize.curve_fit. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html for details.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        xdata (list[list]): The xdata value
        ydata (list[list]): The ydata value
        poly_basic_model (str): The poly_basic_model value Valid options: Horizontal Baseline, Linear Slope Intercept, Quadratic Parabola, Cubic Polynomial, Fourth Order Polynomial, Fifth Order Polynomial, Ninth Order Polynomial, Linear Through X Intercept.

    Returns:
        list[list]: 2D list [param_names, fitted_values, std_errors], or error string.
    """
    def _validate_data(xdata, ydata):
        """Validate and convert both xdata and ydata to numpy arrays."""
        for name, arg in [("xdata", xdata), ("ydata", ydata)]:
            if not isinstance(arg, list) or len(arg) < 2:
                raise ValueError(f"{name}: must be a 2D list with at least two rows")
            vals = []
            for i, row in enumerate(arg):
                if not isinstance(row, list) or len(row) == 0:
                    raise ValueError(f"{name} row {i}: must be a non-empty list")
                try:
                    vals.append(float(row[0]))
                except Exception:
                    raise ValueError(f"{name} row {i}: non-numeric value")
            if name == "xdata":
                x_arr = np.asarray(vals, dtype=np.float64)
            else:
                y_arr = np.asarray(vals, dtype=np.float64)

        if x_arr.shape[0] != y_arr.shape[0]:
            raise ValueError("xdata and ydata must have the same number of rows")
        return x_arr, y_arr

    # Model definitions dictionary
    models = {
        'horizontal_baseline': {
            'params': ['y0'],
            'model': lambda x, y0: np.full_like(x, y0, dtype=float),
            'guess': lambda xa, ya: (float(np.mean(ya)),),
        },
        'linear_slope_intercept': {
            'params': ['A', 'B'],
            'model': lambda x, A, B: A + B * x,
            'guess': lambda xa, ya: (float(np.mean(ya)), float(np.polyfit(xa, ya, 1)[0]) if xa.size > 1 else 0.0),
        },
        'quadratic_parabola': {
            'params': ['A', 'B', 'C'],
            'model': lambda x, A, B, C: A + B * x + C * np.square(x),
            'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, 0.0),
        },
        'cubic_polynomial': {
            'params': ['A', 'B', 'C', 'D'],
            'model': lambda x, A, B, C, D: ((D * x + C) * x + B) * x + A,
            'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, 0.0, 0.0),
        },
        'fourth_order_polynomial': {
            'params': ['A0', 'A1', 'A2', 'A3', 'A4'],
            'model': lambda x, A0, A1, A2, A3, A4: (((A4 * x + A3) * x + A2) * x + A1) * x + A0,
            'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, 0.0, 0.0, 0.0),
        },
        'fifth_order_polynomial': {
            'params': ['A0', 'A1', 'A2', 'A3', 'A4', 'A5'],
            'model': lambda x, A0, A1, A2, A3, A4, A5: ((((A5 * x + A4) * x + A3) * x + A2) * x + A1) * x + A0,
            'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, 0.0, 0.0, 0.0, 0.0),
        },
        'ninth_order_polynomial': {
            'params': ['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9'],
            'model': lambda x, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9: np.polyval([a9, a8, a7, a6, a5, a4, a3, a2, a1, a0], x),
            'guess': lambda xa, ya: (float(np.mean(ya)),) + (0.0,) * 9,
        },
        'linear_through_x_intercept': {
            'params': ['a', 'b'],
            'model': lambda x, a, b: a * (x - b),
            'guess': lambda xa, ya: (float(np.polyfit(xa, ya, 1)[0]) if xa.size > 1 else 1.0, float(np.mean(xa))),
        }
    }

    # Validate model parameter
    if poly_basic_model not in models:
        return f"Invalid model: {str(poly_basic_model)}. Valid models are: {', '.join(models.keys())}"

    model_info = models[poly_basic_model]

    # Validate and convert input data
    try:
        x_arr, y_arr = _validate_data(xdata, ydata)
    except ValueError as e:
        return f"Invalid input: {e}"

    # Perform curve fitting
    try:
        p0 = model_info['guess'](x_arr, y_arr)
        bounds = model_info.get('bounds', (-np.inf, np.inf))
        if bounds == (-np.inf, np.inf):
            popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, maxfev=10000)
        else:
            popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, bounds=bounds, maxfev=10000)

        fitted_vals = [float(v) for v in popt]
        for v in fitted_vals:
            if math.isnan(v) or math.isinf(v):
                return "Fitting produced invalid numeric values (NaN or inf)."
    except ValueError as e:
        return f"Initial guess error: {e}"
    except Exception as e:
        return f"curve_fit error: {e}"

    # Calculate standard errors
    std_errors = None
    try:
        if pcov is not None and np.isfinite(pcov).all():
            std_errors = [float(v) for v in np.sqrt(np.diag(pcov))]
    except Exception:
        pass

    return [model_info['params'], fitted_vals, std_errors] if std_errors else [model_info['params'], fitted_vals]

Online Calculator